/*
 * Copyright (C) 2021 The Chinese Software International Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tonyodev.fetch2.database;

import ohos.app.Context;
import ohos.data.rdb.RdbStore;
import ohos.data.resultset.ResultSet;
import ohos.utils.Pair;

import com.tonyodev.fetch2.PrioritySort;
import com.tonyodev.fetch2.Status;
import com.tonyodev.fetch2.database.migration.Migration;
import com.tonyodev.fetch2.fetch.LiveSettings;
import com.tonyodev.fetch2.util.Defaults;
import com.tonyodev.fetch2core.DefaultStorageResolver;
import com.tonyodev.fetch2core.Extras;
import com.tonyodev.fetch2core.Logger;

import java.util.ArrayList;
import java.util.List;

/**
 * @since 2021-05-24
 */
public class FetchDatabaseManagerImpl implements FetchDatabaseManager {

    private String TAG = "lpq " + FetchDatabaseManagerImpl.class.getSimpleName();

    private LiveSettings liveSettings;
    private boolean fileExistChecksEnabled;
    private DefaultStorageResolver defaultStorageResolver;
    private boolean closed = false;
    private RdbStore database;
    private DownloadDatabase requestDatabase;
    private boolean isClosed;
    private Logger logger;
    private Delegate delegate;

    private FetchDatabaseManagerImpl() {
    }

    public FetchDatabaseManagerImpl(Context context, String namespace) {
        init(context, namespace, null);
    }

    public FetchDatabaseManagerImpl(Context context, String namespace, Logger logger, List<Migration> migrations
        , LiveSettings liveSettings, boolean fileExistChecksEnabled, DefaultStorageResolver defaultStorageResolver) {
        this.logger = logger;
        this.liveSettings = liveSettings;
        this.fileExistChecksEnabled = fileExistChecksEnabled;
        this.defaultStorageResolver = defaultStorageResolver;
        init(context, namespace, migrations);
    }

    private void init(Context context, String namespace, List<Migration> migrations) {
        requestDatabase = new DownloadDatabase_Impl(context, namespace, migrations);
        database = requestDatabase.getRdbStore();
    }

    public boolean isClosed() {
        return closed;
    }

    @Override
    public void setClosed(boolean isClosed) {
        this.isClosed = isClosed;
    }

    @Override
    public Logger getLogger() {
        return this.logger;
    }

    @Override
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    @Override
    public Delegate getDelegate() {
        return this.delegate;
    }

    @Override
    public void setDelegate(Delegate delegate) {
        this.delegate = delegate;
    }


    @Override
    public Pair<DownloadInfo, Boolean> insert(DownloadInfo downloadInfo) {
        if (downloadInfo == null) {
            return null;
        }
        throwExceptionIfClosed();
        long user = requestDatabase.requestDao().insert(downloadInfo);
        System.out.println(TAG + " insert user = " + user);
        boolean row;
        if (user > 0) {
            row = true;
            System.out.println(TAG + "插入关系型数据库成功！");
        } else if (requestDatabase.requestDao().get(downloadInfo.getId()) != null){
            System.out.println(TAG + "已有相同id数据");
            row = true;
        }else{
            row = false;
            System.out.println(TAG + "插入关系型数据库失败");
        }
        return new Pair<>(downloadInfo, row);
    }

    @Override
    public List<Pair<DownloadInfo, Boolean>> insert(List<DownloadInfo> downloadInfoList) {
        if (downloadInfoList == null || downloadInfoList.size() == 0) {
            return null;
        }
        throwExceptionIfClosed();
        List<Long> insert = requestDatabase.requestDao().insert(downloadInfoList);
        ArrayList<Pair<DownloadInfo, Boolean>> list = new ArrayList<>();
        for (int i = 0; i < insert.size(); i++) {
            DownloadInfo downloadInfo = downloadInfoList.get(i);
            Long aLong = insert.get(i);
            list.add(new Pair<>(downloadInfo, aLong > 0 ? true : false));
        }
        return list;
    }

    @Override
    public void delete(DownloadInfo downloadInfo) {
        if (downloadInfo == null) {
            return;
        }
        throwExceptionIfClosed();
        requestDatabase.requestDao().delete(downloadInfo);
    }

    @Override
    public void delete(List<DownloadInfo> downloadInfoList) {
        if (downloadInfoList == null || downloadInfoList.size() == 0) {
            return;
        }
        throwExceptionIfClosed();
        requestDatabase.requestDao().delete(downloadInfoList);
    }

    @Override
    public void deleteAll() {
        requestDatabase.requestDao().deleteAll();
    }

    @Override
    public void update(DownloadInfo downloadInfo) {
        if (downloadInfo == null) {
            return;
        }
        throwExceptionIfClosed();
        requestDatabase.requestDao().update(downloadInfo);
    }

    @Override
    public void update(List<DownloadInfo> downloadInfoList) {
        if (downloadInfoList == null || downloadInfoList.size() == 0) {
            return;
        }
        throwExceptionIfClosed();
        requestDatabase.requestDao().update(downloadInfoList);
    }

    @Override
    public void updateFileBytesInfoAndStatusOnly(DownloadInfo downloadInfo) {
        if (downloadInfo == null) {
            return;
        }
        try {
            database.beginTransaction();
            Object[] object = new Object[4];
            object[0] = downloadInfo.getDownloaded();
            object[1] = downloadInfo.getTotal();
            object[2] = downloadInfo.getStatus().getValue();
            object[3] = downloadInfo.getId();
            database.executeSql("UPDATE " + DownloadDatabase.TABLE_NAME + " SET "
                + DownloadDatabase.COLUMN_DOWNLOADED + " = ?, "
                + DownloadDatabase.COLUMN_TOTAL + " = ?, "
                + DownloadDatabase.COLUMN_STATUS + " = ? "
                + "WHERE " + DownloadDatabase.COLUMN_ID + " = ?", object);
            database.markAsCommit();
        } catch (Exception e) {
            logger.e("DatabaseManager exception", e);
        }
        try {
            database.endTransaction();
        } catch (Exception e) {
            logger.e("DatabaseManager exception", e);
        }
    }

    @Override
    public List<DownloadInfo> get() {
        List<DownloadInfo> downloadInfos = requestDatabase.requestDao().get();
        sanitize(downloadInfos);
        return downloadInfos;
    }

    @Override
    public DownloadInfo get(int id) {
        throwExceptionIfClosed();
        DownloadInfo download = requestDatabase.requestDao().get(id);
        sanitize(download);
        return download;
    }

    @Override
    public List<DownloadInfo> get(List<Integer> ids) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().get(ids);
        sanitize(downloads);
        return downloads;
    }

    @Override
    public DownloadInfo getByFile(String file) {
        throwExceptionIfClosed();
        DownloadInfo download = requestDatabase.requestDao().getByFile(file);
        sanitize(download);
        return download;
    }

    @Override
    public List<DownloadInfo> getByStatus(Status status) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getByStatus(status);
        ArrayList<DownloadInfo> downloadInfos = new ArrayList<>();
        if (sanitize(downloads)) {
            for (DownloadInfo info : downloads) {
                if (info.getStatus() != null && info.getStatus() == status) {
                    downloadInfos.add(info);
                }
            }
        }
        return downloadInfos.size() > 0 ? downloadInfos : downloads;
    }

    @Override
    public List<DownloadInfo> getByStatus(List<Status> statuses) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getByStatus(statuses);
        ArrayList<DownloadInfo> downloadInfos = new ArrayList<>();
        if (sanitize(downloads)) {
            for (DownloadInfo info : downloads) {
                for (Status status : statuses) {
                    if (info.getStatus() != null && info.getStatus() == status) {
                        downloadInfos.add(info);
                    }
                }
            }
        }
        return downloadInfos.size() > 0 ? downloadInfos : downloads;
    }

    @Override
    public List<DownloadInfo> getByGroup(int group) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getByGroup(group);
        sanitize(downloads);
        return downloads;
    }

    @Override
    public List<DownloadInfo> getDownloadsInGroupWithStatus(int groupId, List<Status> statuses) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getByGroupWithStatus(groupId, statuses);
        ArrayList<DownloadInfo> downloadInfos = new ArrayList<>();
        if (sanitize(downloads)) {
            for (DownloadInfo info : downloads) {
                for (Status status : statuses) {
                    if (info.getStatus() != null && info.getStatus() == status) {
                        downloadInfos.add(info);
                    }
                }
            }
        }
        return downloadInfos.size() > 0 ? downloadInfos : downloads;
    }

    @Override
    public List<DownloadInfo> getDownloadsByRequestIdentifier(long identifier) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getDownloadsByRequestIdentifier(identifier);
        sanitize(downloads);
        return downloads;
    }

    @Override
    public List<DownloadInfo> getDownloadsByTag(String tag) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads = requestDatabase.requestDao().getDownloadsByTag(tag);
        sanitize(downloads);
        return downloads;
    }

    @Override
    public List<Integer> getAllGroupIds() {
        throwExceptionIfClosed();
        return requestDatabase.requestDao().getAllGroupIds();
    }

    @Override
    public List<DownloadInfo> getPendingDownloadsSorted(PrioritySort prioritySort) {
        throwExceptionIfClosed();
        List<DownloadInfo> downloads;
        if (prioritySort == PrioritySort.ASC) {
            downloads = requestDatabase.requestDao().getPendingDownloadsSorted(Status.QUEUED);
        } else {
            downloads = requestDatabase.requestDao().getPendingDownloadsSortedDesc(Status.QUEUED);
        }
        if (sanitize(downloads)) {
            for (DownloadInfo info : downloads) {
                if (info.getStatus() != null && info.getStatus() == Status.QUEUED) {
                    downloads.add(info);
                }
            }
        }
        return downloads;
    }

    @Override
    public void sanitizeOnFirstEntry() {
        throwExceptionIfClosed();
        liveSettings.execute(new LiveSettings.LiveSetting() {
            @Override
            public void setLiveSetting(LiveSettings liveSettings) {
                if (!liveSettings.isDidSanitizeDatabaseOnFirstEntry()) {
                    sanitize(get(), true);
                    liveSettings.setDidSanitizeDatabaseOnFirstEntry(true);
                }
            }
        });
    }

    @Override
    public DownloadInfo updateExtras(int id, Extras extras) {
        throwExceptionIfClosed();
        database.beginTransaction();
        Object[] object = new Object[2];
        object[0] = extras.toJSONString();
        object[1] = id;
        database.executeSql("UPDATE " + DownloadDatabase.TABLE_NAME + " SET "
                + DownloadDatabase.COLUMN_EXTRAS + " = ? "
                + " WHERE " + DownloadDatabase.COLUMN_ID + " = ? ",
            object);
        database.markAsCommit();
        database.endTransaction();
        DownloadInfo download = get(id);
        sanitize(download);
        return download;
    }

    private String pendingCountQuery = "SELECT " + DownloadDatabase.COLUMN_ID + " FROM " + DownloadDatabase.TABLE_NAME +
        " WHERE " + DownloadDatabase.COLUMN_STATUS + " = '" + Status.QUEUED.getValue() + "'" +
        " OR " + DownloadDatabase.COLUMN_STATUS + " = '" + Status.DOWNLOADING.getValue() + "'";

    private String pendingCountIncludeAddedQuery = "SELECT " + DownloadDatabase.COLUMN_ID + " FROM " + DownloadDatabase.TABLE_NAME +
        " WHERE " + DownloadDatabase.COLUMN_STATUS + " = '" + Status.QUEUED.getValue() + "'" +
        " OR " + DownloadDatabase.COLUMN_STATUS + " = '" + Status.DOWNLOADING.getValue() + "'" +
        " OR " + DownloadDatabase.COLUMN_STATUS + " = '" + Status.ADDED.getValue() + "'";

    @Override
    public long getPendingCount(boolean includeAddedDownloads) {
        try {
            String query;
            if (includeAddedDownloads) {
                query = pendingCountIncludeAddedQuery;
            } else {
                query = pendingCountQuery;
            }
            String[] columns = new String[]{};
            ResultSet cursor = database.querySql(query, columns);
            if (cursor != null) {
                int rowCount = cursor.getRowCount();
                cursor.close();
                return rowCount;
            }
        } catch (Exception e) {
            return -1;
        }
        return -1;
    }

    @Override
    public DownloadInfo getNewDownloadInfoInstance() {
        throwExceptionIfClosed();
        return new DownloadInfo();
    }

    private void onDownloading(DownloadInfo downloadInfo, boolean firstEntry) {
        if (firstEntry) {
            Status status;
            if (downloadInfo.getDownloaded() > 0 && downloadInfo.getTotal() > 0 && downloadInfo.getDownloaded() >= downloadInfo.getTotal()) {
                status = Status.COMPLETED;
            } else {
                status = Status.QUEUED;
            }
            downloadInfo.setStatus(status);
            downloadInfo.setError(Defaults.getDefaultNoError());
            updatedDownloadsList.add(downloadInfo);
        }
    }

    private void onCompleted(DownloadInfo downloadInfo) {
        if (downloadInfo.getTotal() < 1 && downloadInfo.getDownloaded() > 0) {
            downloadInfo.setTotal(downloadInfo.getDownloaded());
            downloadInfo.setError(downloadInfo.getError());
            updatedDownloadsList.add(downloadInfo);
        }
    }


    private ArrayList<DownloadInfo> updatedDownloadsList = new ArrayList<DownloadInfo>();

    private boolean sanitize(List<DownloadInfo> downloads) {
        return this.sanitize(downloads, false);
    }

    private boolean sanitize(DownloadInfo downloads) {
        return this.sanitize(downloads, false);
    }

    private boolean sanitize(List<DownloadInfo> downloads, boolean firstEntry) {
        updatedDownloadsList.clear();
        for (int i = 0; i < downloads.size(); i++) {
            DownloadInfo downloadInfo = downloads.get(i);
            Status status = downloadInfo.getStatus();
            if (status != null) {
                switch (status) {
                    case COMPLETED:
                        onCompleted(downloadInfo);
                        break;
                    case DOWNLOADING:
                        onDownloading(downloadInfo, firstEntry);
                        break;
                    case QUEUED:
                    case PAUSED:
                        onPaused(downloadInfo);
                        break;
                    case CANCELLED:
                    case FAILED:
                    case ADDED:
                    case NONE:
                    case DELETED:
                    case REMOVED:
                        break;
                }
            }
        }
        int updatedCount = updatedDownloadsList.size();
        if (updatedCount > 0) {
            try {
                update(updatedDownloadsList);
            } catch (Exception e) {
                logger.e("Failed to update", e);
            }
        }
        updatedDownloadsList.clear();
        return updatedCount > 0;
    }

    private void onPaused(DownloadInfo downloadInfo) {
        if (downloadInfo.getDownloaded() > 0) {
            if (fileExistChecksEnabled) {
                if (!defaultStorageResolver.fileExists(downloadInfo.getFile())) {
                    downloadInfo.setDownloaded(0);
                    downloadInfo.setTotal(-1L);
                    downloadInfo.setError(Defaults.getDefaultNoError());
                    updatedDownloadsList.add(downloadInfo);
                    if (delegate != null) {
                        delegate.deleteTempFilesForDownload(downloadInfo);
                    }
                }
            }
        }
    }

    private boolean sanitize(DownloadInfo downloadInfo, boolean initializing) {
        if (downloadInfo == null) {
            return false;
        } else {
            ArrayList<DownloadInfo> list = new ArrayList<>();
            list.add(downloadInfo);
            return sanitize(list, initializing);
        }
    }

    public void close() {
        if (closed) {
            return;
        }
        closed = true;
        try {

        } catch (Exception e) {

        }
        logger.d("Database closed");
    }

    private void throwExceptionIfClosed() {
        if (closed) {
            throw new IllegalStateException("$namespace database is closed");
        }
    }
}
